/*
 * Copyright (c) 2023-2023 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.mybatis.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.elsfs.cloud.common.annotations.Spell;
import org.elsfs.cloud.common.util.lang.ReflectUtils;
import org.elsfs.cloud.common.util.lang.StringUtils;
import org.elsfs.cloud.common.util.pinyin.PinyinUtils;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ClassUtils;

/**
 * MybatisPlus 自动填充配置
 *
 * @author zeng
 */
@Slf4j
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
  /**
   * 填充值，先判断是否有手动设置，优先手动设置的值，例如：job必须手动设置
   *
   * @param fieldName 属性名
   * @param fieldVal 属性值
   * @param metaObject MetaObject
   * @param isCover 是否覆盖原有值,避免更新操作手动入参
   */
  private static void fillValIfNullByName(
      String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) {
    // 0. 如果填充值为空
    if (fieldVal == null) {
      return;
    }

    // 1. 没有 set 方法
    if (!metaObject.hasSetter(fieldName)) {
      return;
    }
    // 2. 如果用户有手动设置的值
    Object userSetValue = metaObject.getValue(fieldName);
    String setValueStr = StringUtils.str(userSetValue, Charset.defaultCharset());
    if (StringUtils.isNotBlank(setValueStr) && !isCover) {
      return;
    }
    // 3. field 类型相同时设置
    Class<?> getterType = metaObject.getGetterType(fieldName);
    if (ClassUtils.isAssignableValue(getterType, fieldVal)) {
      metaObject.setValue(fieldName, fieldVal);
    }
  }

  /**
   * 插入元对象字段填充（用于插入时对公共字段的填充）
   *
   * @param metaObject 元对象
   */
  @Override
  public void insertFill(MetaObject metaObject) {
    LOGGER.debug("mybatis plus start insert fill ....");
    ZonedDateTime now = ZonedDateTime.now();
    fillValIfNullByName("createAt", now, metaObject, true);
    fillValIfNullByName("updateAt", now, metaObject, true);
    fillValIfNullByName("createBy", getUserName(), metaObject, true);
    fillValIfNullByName("updateBy", getUserName(), metaObject, false);
    fillValIfNullByName("deleteFlag", "0", metaObject, true);
    setSpell(metaObject);
  }

  @Override
  public void updateFill(MetaObject metaObject) {
    LOGGER.debug("mybatis plus start update fill ....");
    fillValIfNullByName("updateAt", ZonedDateTime.now(), metaObject, false);
    fillValIfNullByName("updateBy", getUserName(), metaObject, true);
    setSpell(metaObject);
  }

  /**
   * 获取 spring security 当前的用户名
   *
   * @return 当前用户名
   */
  private String getUserName() {
    var authentication = SecurityContextHolder.getContext().getAuthentication();
    // 匿名接口直接返回
    if (authentication instanceof AnonymousAuthenticationToken) {
      return "";
    }

    if (Optional.ofNullable(authentication).isPresent()) {
      return authentication.getName();
    }

    return null;
  }

  private void setSpell(MetaObject metaObject) {
    Object obj = metaObject.getOriginalObject();
    // 拼音
    Field spellField = ReflectUtils.getClassFirstAnnotationFieldName(obj, Spell.class);
    if (spellField != null) {
      Object spellValue = ReflectUtils.getObjValueToStr(obj, spellField);
      if (Objects.nonNull(spellValue)) {
        String dataSpell = PinyinUtils.toDefPinyin(spellValue.toString());
        this.strictInsertFill(metaObject, "pinyinCode", String.class, dataSpell);
        String dataWbspell = PinyinUtils.toDefWubi(spellValue.toString());
        this.strictInsertFill(metaObject, "wubiCode", String.class, dataWbspell);
      }
    }
  }
}
